package square;
import java.util.ArrayList;
import java.util.List;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;

/**
 * A class of squares involving a temperature and a humidity.
 * 
 * @invar	The temperature of each square must be a valid temperature
 * 			for that square.
 * 			| canHaveAsTemperature(getTemparture(Temperature.CELCIUS),Temperature.CELCIUS)
 * @invar 	The humidity of each square must be a valid humidity
 * 			for a square.
 * 			| isValidHumidity(getHumidity());
 * @invar	The maximum temperature must be a valid temperature.
 * 			| canHaveAsMaximumTemperature(getMaximumTemperature(),Temperature.CELCIUS)
 * @invar	The minimum temperature must be a valid temperature.
 * 			| canHaveAsMinimumTemperature(getMinimumTemperature(),Temperature.CELCIUS)
 * @invar	The weight for weighted average of temperatures must be a valid number.
 * 			| isValidConstantForWeightedAverageOfTemperature(
 * 								getConstantForWeightedAverageOfTemperature)
 * @invar	The increment size for heat damage must be a valid number.
 * 			| isValidIncrementSizeHeatDamage(getIncrementSizeHeatDamage(Temperature.CELCIUS))
 * @invar	The threshold temperature for heat damage must be a valid temperature.
 * 			| isValidThresholdHeatDamageTemperature(
 * 						getThresholdHeatDamageTemperature(Temperature.CELCIUS),
 * 						Temperature.CELCIUS)
 * 
 * @version	1.0
 * @author	Dries Liebens 
 * 			Loic Vercruysse
 */
public class Square {

	/**
	 * Initialize this new square with a temperature equal to 20 degrees Celcius
	 * and a humidity of 0%.
	 * 
	 * @effect	The new square is initialized with a temperature of 20 degrees Celcius.
	 * 			and a humidity of 0%.
	 * 			| this(20,Temperature.CELCIUS,0)
	 */
	public Square() {
		this(20,Temperature.CELCIUS,0);
	}

	/**
	 * Initialize this new square with given temperature and given humidity.
	 * 
	 * @param	temperature
	 * 			The temperature for this new square in the preferred scale.
	 * @param	humidity
	 * 			The humidity for this new square.
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 * @pre		The given temperature must be a valid temperature.
	 * 			| canHaveAsTemperature(temperature)
	 * @pre		The given humidity must be a valid humidity.
	 * 			| isValidHumidity(humidity)
	 * @post	The temperature of this new square is equal to the given temperature.
	 * 			| new.getTemperature() == temperature
	 * @post	The humidity of this new square is equal to the given humidity.
	 * 			| new.getHumidity() == humidity
	 * @post	The maximum temperature is initializated at 5000 degrees Celcius.
	 * 			| new.getMaximumTemperature(Temperature.CELCIUS) == 5000
	 * @post	The minimum temperature is initializated at minus 200 degreed Celcius.
	 * 			| new.getMinimumTemperature(Temperature.CELCIUS) == -200
	 * @post	The threshold temperature for heat damage is initializated at 35 degrees Celcius.
	 * 			| new.getThresholdHeatDamageTemperature(Temperature.CELCIUS) == 35
	 * @post	The constant for weighted average for temperature is initializated at 0,2 .
	 * 			| new.getConstantForWeightedAverageOfTemperature() == 0.2
	 * @post	The increment size for heat damage is initializated at 15 for
	 * 			the temperature scale Celcius.
	 * 			| new.getIncrementSizeHeatDamage(Temperature.CELCIUS) == 15
	 * @post	This square is initializated that it isn't made of slippery material.
	 * 			| new.isMadeOfSlipperyMaterial() == false;
	 * @throws	IllegalArgumentException
	 * 			The given temperature is not a valid temperature for this square.
	 * 			| !canHaveAsTemperature(temperature)
	 * @throws	IllegalArgumentException
	 * 			The temperature scale is not valid.
	 * 			| !isValidTemperatureScale(scale)
	 */
	public Square(double temperature, Temperature scale, double humidity) throws IllegalArgumentException {
		if(!isValidTemperatureScale(scale))
			throw new IllegalArgumentException("Non-valid scale!");
		
		setHumidity(humidity);
		this.maximumTemperature = 5000;
		this.minimumTemperature = -200;
		setTemperature(temperature,scale);
		setThresholdHeatDamageTemperature(35, Temperature.CELCIUS);
		setConstantForWeightedAverageOfTemperature(0.2);
		setIncrementSizeHeatDamage(15, Temperature.CELCIUS);
		setMadeOfSlipperyMaterial(false);
	}	
	
	/**
	 * Checks whether the given temperature scale is a valid temperature scale
	 * for a square.
	 * 
	 * @param	scale
	 * 			The temperature scale to check.	 * 
	 * @return	True if the temperature scale is Celcius, Fahrenheit or Kelvin.
	 * 			| result == (scale == Temperature.CELCIUS) 
	 * 			|			|| (scale == Temperature.FahrenHeit)
	 * 			|			|| (scale == Temperature.Kelvin)
	 */
	@Raw
	public static boolean isValidTemperatureScale(Temperature scale){
		return ( (scale == Temperature.CELCIUS) || (scale == Temperature.FAHRENHEIT) 
						|| (scale == Temperature.KELVIN) );
	}
	
	/**
	 * Returns the temperature in the preferred scale of this square.
	 * 
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 */
	@Basic @Raw
	public double getTemperature(Temperature scale) {
		return Temperature.CELCIUS.convertTo(this.temperature, scale);
	}
	
	/**
	 * Sets the given temperature as the temperature.
	 * 
	 * @param	temperature
	 * 			The temperature in the preferred scale to set.
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 * @post	The temperature is the same as the given temperature.
	 * 			| new.getTemperature(scale) == temperature
	 * @throws	IllegalArgumentException
	 * 			The temperature is not valid.
	 * 			| !isValidTemperature(temperature,scale)
	 * @throws	IllegalArgumentException
	 * 			The temperature scale is not valid.
	 * 			| !isValidTemperatureScale(scale)
	 */
	@Raw
	public void setTemperature(double temperature,Temperature scale) throws IllegalArgumentException{
		if(!canHaveAsTemperature(temperature,scale))
			throw new IllegalArgumentException("Non-valid temperature!");
		if(!isValidTemperatureScale(scale))
			throw new IllegalArgumentException("Non-valid scale!");
		this.temperature = scale.convertTo(temperature, Temperature.CELCIUS);
	}	
	
	/**
	 * Checks whether this square can have the given temperature as temperature.
	 * 
	 * @param	temperature
	 * 			The temperature in the preferred scale to check.
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 * @return	True if the temperature is higher or equal to the minimum temperature
	 * 			and lower or equal to the maximum temperature.
	 * 			| result == temperature <= getMaximumTemperature(scale))
	 * 			|				&& (temperature >= getMinimumTemperature(scale))
	 */
	public boolean canHaveAsTemperature(double temperature,Temperature scale){
		return ( (temperature <= getMaximumTemperature(scale)) 
					&& (temperature >= getMinimumTemperature(scale)) );
	}
	
	/**
	 * Variable referencing the temperature in degrees Celcius of this square.
	 */
	private double temperature;
	
	/**
	 * Returns the maximum temperature in the preferred scale of this square.
	 * 
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 */
	@Basic @Raw
	public double getMaximumTemperature(Temperature scale){
		return Temperature.CELCIUS.convertTo(this.maximumTemperature, scale);
	}
	
	/**
	 * Sets the given temperature as maximum temperature.
	 * 
	 * @param	maximumTemperature
	 * 			The maximum temperature in the preferred scale to set.
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 * @post	The maximum temperature is the same as the given maximum temperature.
	 * 			| new.getMaximumTemperature(scale) == maximumTemperature
	 * @throws	IllegalArgumentException
	 * 			The maximum temperature is not valid.
	 * 			| !isValidMaximumTemperature(maximumTemperature,scale)
	 * @throws	IllegalArgumentException
	 * 			The temperature scale is not valid.
	 * 			| !isValidTemperatureScale(scale)
	 */
	@Raw
	public void setMaximumTemperature(double maximumTemperature,Temperature scale) throws IllegalArgumentException{
		if(!canHaveAsMaximumTemperature(maximumTemperature,scale))
			throw new IllegalArgumentException("Non-valid maximum temperature!");
		if(!isValidTemperatureScale(scale))
			throw new IllegalArgumentException("Non-valid scale!");
		this.maximumTemperature = scale.convertTo(maximumTemperature, Temperature.CELCIUS);
	}
	
	/**
	 * Checks whether this square can have the given temperature as maximum temperature.
	 * 
	 * @param	maximumTemperature
	 * 			The temperature in the preferred scale to check.
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 * @return	True if maximumTemperature is higher or equal then the minimum temperature
	 * 			and maximumTemperature is higher or equal then the current temperature
	 * 			| result == ( (maximumTemperature >= getMinimumTemperature(scale)) 
	 * 			|				&& (maximumTemperature >= getTemperature(scale)) )
	 */
	public boolean canHaveAsMaximumTemperature(double maximumTemperature,Temperature scale){
		return ( (maximumTemperature >= getMinimumTemperature(scale)) 
					&& (maximumTemperature >= getTemperature(scale)) );
	}
	
	/**
	 * Variable referencing the maximum temperature in degrees Celcius of this square.
	 */
	private double maximumTemperature;
	
	/**
	 * Returns the minimum temperature in the preferred scale of this square.
	 * 
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 */
	@Basic @Raw
	public double getMinimumTemperature(Temperature scale){
		return Temperature.CELCIUS.convertTo(this.minimumTemperature,scale);
	}

	/**
	 * Sets the given temperature as minimum temperature.
	 * 
	 * @param	minimumTemperature
	 * 			The minimum temperature in the preferred scale to set.
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 * @post	The minimum temperature is the same as the given minimum temperature.
	 * 			| new.getMinimumTemperature(scale) == minimumTemperature
	 * @throws	IllegalArgumentException
	 * 			The minimum temperature is not valid.
	 * 			| !isValidMinimumTemperature(minimumTemperature,scale)
	 * @throws	IllegalArgumentException
	 * 			The temperature scale is not valid.
	 * 			| !isValidTemperatureScale(scale)
	 */
	@Raw
	public void setMinimumTemperature(double minimumTemperature,Temperature scale) throws IllegalArgumentException{
		if(!canHaveAsMinimumTemperature(minimumTemperature,scale))
			throw new IllegalArgumentException("Non-valid minimum temperature!");
		if(!isValidTemperatureScale(scale))
			throw new IllegalArgumentException("Non-valid scale!");
		this.minimumTemperature = scale.convertTo(minimumTemperature,Temperature.CELCIUS);
	}
	
	/**
	 * Checks whether this square can have the given temperature as minimum temperature.
	 * 
	 * @param	minimumTemperature
	 * 			The temperature in the preferred scale to check.
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 * @return	True if minimumTemperature is lower or equal then the maximum temperature
	 * 			and minimumTemperature is lower or equal then the current temperature
	 * 			and minimumTemperture is higher then 0 Kelvin.
	 * 			| result == ( (minimumTemperature <= getMaximumTemperature(scale)) 
	 * 			|				&& (minimumTemperature <= getTemperature(scale)) 
	 * 			|				&& (minimumTemperature >= Temperature.KELVIN.convertTo(0,scale) )
	 */
	public boolean canHaveAsMinimumTemperature(double minimumTemperature,Temperature scale){
		return ( (minimumTemperature <= getMaximumTemperature(scale)) 
					&& (minimumTemperature <= getTemperature(scale)) 
					&& (minimumTemperature >= Temperature.KELVIN.convertTo(0,scale)) );
	}

	/**
	 * Variable referencing the minimum temperature in degrees Celcius of this square.
	 */
	private double minimumTemperature;

	/**
	 * Returns the humidity in percents of this square.
	 */
	@Basic @Raw
	public double getHumidity() {
		return this.humidity;
	}
	
	/**
	 * Sets the given humidity as humidity of this square.
	 * 
	 * @param	humidity
	 * 			The humidity to set.
	 * @pre		The given humidity must be a valid percentage.
	 * 			| isValidHumidity(humidity)
	 * @post	The humidity of this square is equal to the given humidity/
	 * 			| new.getHumidity() == humidity
	 */
	@Raw
	public void setHumidity(double humidity) {
		assert isValidHumidity(humidity);
		this.humidity = humidity;
	}
	
	/**
	 * Checks whether this square can have the given humidity as humidity.
	 * 
	 * @param	humidity
	 * 			The humidity to check.
	 * @return	True if humidity is equal or lower then 100 and humidity is equal or higher then 0,
	 * 			humidity may also have maximum 2 numbers after the comma.
	 * 			| result == ( (humidity >= 0) && (humidity <= 100) 
	 * 			|				&& (humidity*100)%1 == 0) )
	 */
	@Raw
	public static boolean isValidHumidity(double humidity) {
		return ( (humidity >= 0) && (humidity <= 100) && ((humidity*100)%1 == 0) );
	}
	
	/**
	 * Calculates the rust damage due the humidity of this square.
	 * 
	 * @return	The humidity is first substract by 30 and then devided by 7.
	 * 			| result == (getHumidity() - 30) / 7
	 */
	public int calculateRustDamage() {
		Double rustDamage =  (getHumidity() - 30)/7;
		if(rustDamage % 1 != 0)
			rustDamage = rustDamage%1;
		return rustDamage.intValue();
	}
	
	/**
	 * Variable referencing the humidity of this square.
	 */
	private double humidity; 
	
	/**
	 * Returns the threshold for heat damage in the preferred scale for squares.
	 * 
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 * @throws	IllegalArgumentException
	 * 			The temperature scale is not valid.
	 * 			| !isValidTemperatureScale(scale)
	 */
	@Basic @Raw
	public static double getThresholdHeatDamageTemperature(Temperature scale) throws IllegalArgumentException {
		if(!isValidTemperatureScale(scale))
			throw new IllegalArgumentException("Non-valid scale!");
		return Temperature.CELCIUS.convertTo(Square.thresholdHeatDamageTemperature,scale);
	}
	
	/**
	 * Sets the threshold for heat damage in the preferred scale for squares.
	 * 
	 * @param	thresholdHeatDamageTemperature
	 * 			The temperature to set as threshold for heat damage in the preferred scale.
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 * @post	The threshold for heat damage for squares is equal to the given temperature.
	 * 			new.getThresholdHeatDamageTemperature(scale) == thresholdHeatDamageTemperature
	 * @throws	IllegalArgumentException
	 * 			The given temperature is not valid.
	 * 			| !isValidThresholdHeatDamageTemperature(thresholdHeatDamageTemperature,scale)
	 * @throws	IllegalArgumentException
	 * 			The temperature scale is not valid.
	 * 			| !isValidTemperatureScale(scale)
	 */
	@Raw
	public static void setThresholdHeatDamageTemperature(double thresholdHeatDamageTemperature,Temperature scale) 
		throws IllegalArgumentException {
		if(!isValidThresholdHeatDamageTemperature(thresholdHeatDamageTemperature,scale))
			throw new IllegalArgumentException("Non-valid temperature!");
		if(!isValidTemperatureScale(scale))
			throw new IllegalArgumentException("Non-valid temperature scale");
		Square.thresholdHeatDamageTemperature = scale.convertTo(thresholdHeatDamageTemperature, Temperature.CELCIUS);
	}
	
	/**
	 * Checks whether this square can have the given thresholdHeatDamageTemperature as thresholdHeatDamageTemperature.
	 * 
	 * @param	thresholdHeatDamageTemperature
	 * 			The temperature in the preferred scale to check.
	 * @param	scale
	 * 			The scale in which the temperature is expressed in. 
	 * @return	True if thresholdHeatDamageTemperature is higher then the threshold for cold damage.
	 * 			| result == (thresholdHeatDamageTemperature > getThresholdColdDamageTemperature(scale))
	 */
	@Raw
	public static boolean isValidThresholdHeatDamageTemperature(double thresholdHeatDamageTemperature,Temperature scale) {
		return thresholdHeatDamageTemperature > getThresholdColdDamageTemperature(scale);
	}
	
	/**
	 * Variable referencing the threshold for heat damage in degrees Celcius this square.
	 */
	private static double thresholdHeatDamageTemperature;
	
	/**
	 * Returns the threshold for cold damage in the preferred scale for squares.
	 * 
	 * @param	scale
	 * 			The scale in which the temperature is expressed in.
	 */
	@Basic @Raw
	public static double getThresholdColdDamageTemperature(Temperature scale) {
		return Temperature.CELCIUS.convertTo(thresholdColdDamageTemperature, scale);
	}
	
	/**
	 * Variable referencing the threshold for cold damage in degrees Celcius for squares.
	 */
	public static final double thresholdColdDamageTemperature = -5;
	
	/**
	 * Returns the increment size based on the preferred temperature scale for squares.
	 * 
	 * @param	scale
	 * 			The temperature scale in which the increment size has to be calculated.
	 */
	@Basic @Raw
	public static double getIncrementSizeHeatDamage(Temperature scale) {
		return Temperature.CELCIUS.convertTo(0,scale) 
				+ Temperature.CELCIUS.convertTo(incrementSizeHeatDamage,scale);
	}
	
	/**
	 * Sets the increment size for heat damage.
	 * 
	 * @param	incrementSizeHeatDamage
	 * 			The increment size to set as increment size for heat damage.
	 * @param	scale
	 * 			the temperature scale in which the increment size is given.
	 * @post	The increment size for heat damage is equal to the given increment size for heat damage.
	 * 			| new.getIncrementSizeHeatDamage == incrementSizeHeatDamage()
	 * @throws	IllegalArgumentException
	 * 			The given increment size is not valid.
	 * 			| !(isValidIncrementSizeHeatDamage())
	 * @throws	IllegalArgumentException
	 * 			The temperature scale is not valid.
	 * 			| !isValidTemperatureScale(scale)
	 */
	@Raw
	public static void setIncrementSizeHeatDamage(double incrementSizeHeatDamage,Temperature scale) 
		throws IllegalArgumentException {
		if(!isValidIncrementSizeHeatDamage(incrementSizeHeatDamage))
			throw new IllegalArgumentException("Non-valid increment size!");
		if(!isValidTemperatureScale(scale))
			throw new IllegalArgumentException("Non-valid scale!");
		
		double incrementSizeHeatDamageInCelciusScale = incrementSizeHeatDamage + Temperature.CELCIUS.convertTo(0, scale);
		Square.incrementSizeHeatDamage = scale.convertTo(incrementSizeHeatDamageInCelciusScale, 
														Temperature.CELCIUS);
	}
	
	/**
	 * Checks if squares can have the given incrementSizeHeatDamage as incrementSizeHeatDamage.
	 * 
	 * @param	incrementSizeHeatDamage
	 * 			The increment size to check.
	 * @return	True if incrementSizeHeatDamage is higher then zero.
	 * 			| result == incrementSizeHeatDamage > 0
	 */
	@Raw
	public static boolean isValidIncrementSizeHeatDamage(double incrementSizeHeatDamage) {
		return incrementSizeHeatDamage > 0;
	}
	
	/**
	 *	Variable referencing the increment size for heat damage for squares.
	 *		This increment size is calculated on the scale degrees Celcius. 
	 */
	private static double incrementSizeHeatDamage;
	
	/**
	 * Calculates the heat damage due the temperature of this square.
	 * 
	 * @return	zero if the temperature is lower then the threshold temperature for heat damage.
	 * 			| if(getTemperature(Temperature.CELCIUS) <= getThresholdHeatDamageTemperature(Temperature.CELCIUS))
	 * 			|	then result == 0;
	 * @return 	the heat damage of this square, this is calculated by following expression:
	 * 				if the temperature of this square is higher then the threshold for heat damage
	 * 				then the result is the absolute value of the temperature minus the absolute temperature of
	 * 				the threshold temperature for heat damage and this divided by the increment size for heat damage.
	 * 				The heat damage is also adjusted upwards.
	 * 			| if(getTemperature(Temperature.CELCIUS) > getThresholdHeatDamageTemperature(Temperature.CELCIUS))
	 * 			|	then result == 
	 * 			|	(Math.abs(getTemperature(Temperature.CELCIUS))
	 * 			|		- Math.abs(getThresholdHeatDamageTemperature(Temperature.CELCIUS)))/getIncrementSizeHeatDamage()
	 */
	public int calculateHeatDamage() {
		if(getTemperature(Temperature.CELCIUS) <= getThresholdHeatDamageTemperature(Temperature.CELCIUS))
			return 0;
		Double heatDamage = (getTemperature(Temperature.CELCIUS) 
				- getThresholdHeatDamageTemperature(Temperature.CELCIUS))/getIncrementSizeHeatDamage(Temperature.CELCIUS);
		if( (heatDamage % 1) != 0)
			heatDamage = (heatDamage / 1);
		return heatDamage.intValue();
	}
	
	/**
	 * Calculates the cold damage due the temperature of this square.
	 * 
	 * @return	zero if the temperature is higher then the threshold temperature
	 * 			for cold damage.
	 * 			| if(getTemperature(Temperature.CELCIUS) >= -5)
	 * 			|	then result == 0;
	 * @return	the cold damage of this square, this is calculated by following expression:
	 * 				if the temperature of this square is lower then the threshold for cold damage
	 * 				then the result is the absolute value of the temperature of this square minus
	 * 				the absolute value of the threshold temperature for cold damage and this divided by
	 * 				ten.
	 * 			| if(getTemperature(Temperature.CELCIUS) < -5)
	 * 			|	then result == 
	 * 			|	( Math.abs(getTemperature(Temperature.CELCIUS)
	 * 			|		- Math.abs(getThresholdColdDamageTemperature(Temperature.CELCIUS)))/10)
	 */
	public int calculateColdDamage() {
		if(getTemperature(Temperature.CELCIUS) >= getThresholdColdDamageTemperature(Temperature.CELCIUS))
			return 0;
		Double coldDamage =  (Math.abs(getTemperature(Temperature.CELCIUS)
					- Math.abs(getThresholdColdDamageTemperature(Temperature.CELCIUS)))/10);
		if( (coldDamage % 1) != 0)
			coldDamage = coldDamage / 1;
		return coldDamage.intValue();
	}

	/**
	 * Sets if this square is made of slippery material.
	 * 
	 * @param	madeOfSlipperyMaterial
	 * 			Boolean if this square is made of slippery material.
	 * @post	The boolean if this square is made of slippery material of this square 
	 * 			is equal to the given boolean.
	 * 			| new.ismadeOfSlipperyMaterial() == madeOfSlipperyMaterial
	 */
	@Raw
	public void setMadeOfSlipperyMaterial(boolean madeOfSlipperyMaterial) {
		this.madeOfSlipperyMaterial = madeOfSlipperyMaterial;
	}	
	
	/**
	 * Returns if this square is made of slippery material.
	 */
	@Basic @Raw
	public boolean isMadeOfSlipperyMaterial() {
		return this.madeOfSlipperyMaterial;
	}

	/**
	 * Variable referencing if this square is made of slippery material.
	 */
	private boolean madeOfSlipperyMaterial;
	
	/**
	 * Calculates if the floor is slippery
	 * 
	 * @return	True if one of the following cases happens:
	 * 				* the floor is made of slippery material
	 * 				* the temperature is above zero degrees Celcius and
	 * 					the humidity is 100%.
	 * 				* the temperature is below ore equal to zero degrees Celcius
	 * 					and the humidity is above 10%.
	 * 			| if(isMadeOfSlipperyMaterial())
	 * 			|	then result == true
	 * 			| else if(getTemperature(Temperature.CELCIUS) > 0) && (getHumidity() == 100))
	 * 			|	then result == true
	 * 			| else if((getTemperature(Temperature.CELCIUS) <= 0) && (getHumidity() > 10))
	 * 			|	then result == true
	 * 			| else
	 * 			|	result == false
	 */
	public boolean isSlippery(){ 
		if(isMadeOfSlipperyMaterial())
			return true;
		if((getTemperature(Temperature.CELCIUS) > 0) && (getHumidity() == 100))
			return true;
		if((getTemperature(Temperature.CELCIUS) <= 0) && (getHumidity() > 10))
			return true;
		return false;
	}
	
	/**
	 * Calculates the inhabitability of this square.
	 * 
	 * @return	The value for inhabitability, which is calculated by following expression:
	 * 				The root of the third power of the heat damage,
	 * 				divided by the root of 101 minus the humidity, this whole part
	 * 				then added with the root of cold damage.
	 * 				That result multiplied with minus one is the final result
	 * 			| result == -Math.sqrt(getHeatDamage()^3)/Math.sqrt(101-getHumidity()) 
	 * 			|				- Math.sqrt(calculateColdDamage()) 
	 */
	public double calculateInhabitability() {
		double rootOfTriplePowerOfHeatDamage = Math.sqrt(calculateHeatDamage() * calculateHeatDamage()
				* calculateHeatDamage());
		double rootOfHumidityWithConstant = Math.sqrt(101 - getHumidity());
		double rootOfColdDamage = Math.sqrt(calculateColdDamage());
		return -(rootOfTriplePowerOfHeatDamage / rootOfHumidityWithConstant) - rootOfColdDamage;
	}
	
	/**
	 * Add one or more borders to this square.
	 * 
	 * @param	directions
	 * 			the direction()s in which the border(s) has to be set.
	 * @post	the border is set in given direction.
	 * 			| new.getBorder(direction) == true
	 * @post	if there already exists a border in the given direction,
	 * 			the original value is kept.
	 * 			| if(hasBorder(direction)
	 * 			|	then new.getBorder(direction) == this.getBorder(direction);
	 * @post	if the borders are not effective, there are no changes
	 * 			| if(directions == null)
	 * 			| 	then new.getBorders() == this.getBorders()
	 */
	@Raw
	public void addBorders(Direction... directions) {
		if(directions != null)
			for(Direction direction : directions){
				if(!hasBorder(direction))
					this.borders.add(direction.getSymbol());
			}
	}
	
	/**
	 * Removes one or more borders of this square.
	 * 
	 * @param	directions
	 * 			the directions in which the borders have to be removed.
	 * @post	the border  in given direction is removed.
	 * 			| new.getBorder(direction) == false
	 * @post	if there didn't exists a border in the given direction,
	 * 			then nothing changes.
	 * 			| if(!hasBorder(direction)
	 * 			|	then new.getBorder(direction) == this.getBorder(direction);
	 * @post	if the borders are not effective, there are no changes
	 * 			| if(directions == null)
	 * 			| 	then new.getBorders() == this.getBorders()
	 */
	@Raw
	public void removeBorder(Direction... directions) {
		if(directions != null)
			for(Direction direction : directions){
				if(hasBorder(direction))
					this.borders.remove(borders.indexOf(direction.getSymbol()));
			}
	}
	
	/**
	 * Removes all the borders of this square.
	 * 
	 * @post	the square has no borders.
	 * 			| for each Direction direction :
	 * 			|	square.hasBorder(direction) == false
	 * 
	 * @effect	the method removeBorder is called for all borders.
	 * 			| this.removeBorder(Direction.ONE, Direction.TWO,
	 * 			|				Direction.THREE, Direction.FOUR,
	 * 			|				Direction.FIVE, Direction.SIX)
	 */
	@Raw
	public void removeAllBorders() {
		removeBorder(Direction.ONE, Direction.TWO, Direction.THREE, 
				Direction.FOUR, Direction.FIVE, Direction.SIX);
	}
	
	/**
	 * Returns if this square has a border at the given direction.
	 * 
	 * @param	direction
	 * 			The direction to check if there is a border.
	 * @return	True if this square has a border at the given direction.
	 * 			| getBorders().contains(direction.getSymbol())
	 */
	@Raw
	public boolean hasBorder(Direction direction) {
		return getBorders().contains(direction.getSymbol());
	}
	
	/**
	 * Returns the borders of this square.
	 */
	@Basic @Raw
	public List<Character> getBorders() {
		return new ArrayList<Character>(borders);
	}
		
	/**
	 * Variable referencing the borders of this square.
	 */
	private List<Character> borders = new ArrayList<Character>();	
	
	/**
	 * Returns the constant for weighted average for temperatures for squares.
	 */
	@Basic @Raw
	public static double getConstantForWeightedAverageOfTemperature() {
		return Square.constantForWeightedAverageOfTemperature;
	}
	
	/**
	 * Sets the constant for weighted average for temperatures.
	 * 
	 * @param	constantForWeightedAverageOfTemperature
	 * 			The constant to set for weighted average for temperatures.
	 * @post	The constant for weighted average for temperatures is equal to the given constant.
	 * 			| new.getConstantForWeightedAverageOfTemperature() == constantForWeightedAverageOfTemperature
	 * @throws	IllegalArgumentException
	 * 			The given constant is not valid.
	 * 			| !isValidConstantForWeightedAverageOfTemperature(constantForWeightedAverageOfTemperature)
	 */
	@Raw
	public static void setConstantForWeightedAverageOfTemperature(double constantForWeightedAverageOfTemperature) 
		throws IllegalArgumentException {
		if(!isValidConstantForWeightedAverageOfTemperature(constantForWeightedAverageOfTemperature))
			throw new IllegalArgumentException();
		Square.constantForWeightedAverageOfTemperature = constantForWeightedAverageOfTemperature;
	}
	
	/**
	 * Checks if squares can have the given constant as constant for weighted average for temperatures.
	 * 
	 * @param	constantForWeightedAverageOfTemperature
	 * 			The constant to check.
	 * @return	True if constantForWeightedAverageOfTemperature is lower or equal to 0.4 or 
	 * 			constantForWeightedAverageOfTemperature is higher or equal to 0.1
	 * 			| result =((constantForWeightedAverageOfTemperature >= 0.1) 
	 * 			|		&& (constantForWeightedAverageOfTemperature <= 0.4))
	 */
	@Raw
	public static boolean isValidConstantForWeightedAverageOfTemperature(double constantForWeightedAverageOfTemperature) {
		return ( (constantForWeightedAverageOfTemperature >= 0.1) && (constantForWeightedAverageOfTemperature <= 0.4) );
	}
	
	/**
	 * Variable referencing the constant for weighted average of temperatures of squares.
	 */
	public static double constantForWeightedAverageOfTemperature;
	
	/**
	 * Merges the given square with this square in given direction.
	 * 
	 * @param	square
	 * 			The square to be merged with.
	 * @param	direction
	 * 			The direction in which the squares are merged.
	 * @post	In both squares, there is no border in the given direction.
	 * 			| new.getBorder(direction) == (new square).getBorder(direction) == false
	 * @post	The new humidity in the given square and this is equal to the average
	 * 			of the two squares.
	 * 			| new.getHumidity() == calculateAverage(getHumidity(),square.getHumidity())
	 * 			| (new square).getHumidity() == calculateAverage(getHumidity(),square.getHumidity())
	 * @post	The new temperature in the given square and this is equal
	 * 			to the weighted average of the two squares.
	 * 			| new.getTemperature() == calculateWeightedAverage(getTemperature,square.getTemperature()
	 * 			| (new square).getTemperature() == calculateWeightedAverage(getTemperature,square.getTemperature()
	 * @post	If one of the squares is made of slippery material, 
	 * 			the new squares are then made of slippery material.
	 * 			| if(square.isMadeOfSlipperyMaterial() || this.isMadeOfSlipperyMaterial())
	 * 				(new square).isMadeOfSlipperMaterial == true
	 * 				(new this).isMadeOfSlipperyMaterial == true
	 * @throws	IllegalArgumentException
	 * 			square is non effective.
	 * 			| square == null
	 * @throws	IllegalArgumentException
	 * 			direction is non effective.
	 * 			| direction == null
	 */
	public void mergeWith(Square square, Direction direction) throws IllegalArgumentException{
		if(square == null)
			throw new IllegalArgumentException();
		if(direction == null)
			throw new IllegalArgumentException();
		
		// delete borders
		square.removeBorder(direction);
		this.removeBorder(direction);
		// set humidity
		double averageHumidity = calculateAverage(square.getHumidity(),getHumidity());
		square.setHumidity(averageHumidity);
		this.setHumidity(averageHumidity);
		// set temperature
		double weightedAverageTemperature = calculateWeightedAverage(square.getTemperature(Temperature.CELCIUS),
												getTemperature(Temperature.CELCIUS) );
		square.setTemperature(weightedAverageTemperature, Temperature.CELCIUS);
		this.setTemperature(weightedAverageTemperature, Temperature.CELCIUS);
		// set slippery
		if(square.isMadeOfSlipperyMaterial() || this.isMadeOfSlipperyMaterial())
			square.setMadeOfSlipperyMaterial(true);
			this.setMadeOfSlipperyMaterial(true);
	}
	
	/**
	 * Calculates the average of the two given numbers.
	 * 
	 * @param	numberOne
	 * 			The first number of the two involved in the calculation.
	 * @param	numberTwo
	 * 			The second number of the two involved in the calculation.
	 * @return	The average of the two numbers
	 * 			| result == (numberOne + numberTwo)/2
	 */
	@Raw
	public double calculateAverage(double numberOne,double numberTwo) {
		return (numberOne + numberTwo)/2;
	}
	
	/**
	 * Calculates the weighted average of the two given numbers.
	 * 
	 * @param	numberOne
	 * 			The first number of the two involved in the calculation.
	 * @param	numberTwo
	 * 			The second number of the two involved in the calculation.
	 * @return	The result is weightOne multiplied by numberOne plus weightTwo multiplied by numberTwo
	 * 			and then divided by two.
	 * 			The weights are calculated by the methods calculateWeight().
	 * 			| let
	 * 			|	average == calculateAverage(numberOne,numberTwo)
	 * 			|	weightOne == calculateWeight(numberOne,average)
	 * 			|	weightTwo == calculateWeight(numberTwo,average)
	 * 			| in result == (average*numberOne + weightTwo*numberTwo)2
	 */
	@Raw
	public double calculateWeightedAverage(double numberOne,double numberTwo) {
		double average = calculateAverage(numberOne,numberTwo);
		double weightOne = calculateWeight(numberOne,average);
		double weightTwo = calculateWeight(numberTwo,average);
		return (weightOne*numberOne + weightTwo*numberTwo)/2;
	}
	
	/**
	 * Calculates the weight for a weighted average.
	 * 
	 * @param	number
	 * 			Number for which the weight is calculated.
	 * @param	average
	 * 			The average of the numbers which are involved
	 * 			in the weighted average.
	 * @return	The result is one minus the constant for weighted average of temperature
	 * 			multiplied with the number and divided by average and then added with
	 * 			the constant for weighted average of temperature.
	 * 			| result == (1-getConstantForWeightedAverageOfTemperature())*number/average
	 *							+ getConstantForWeightedAverageOfTemperature()
	 */
	@Raw
	public double calculateWeight(double number,double average) {
		return (1-getConstantForWeightedAverageOfTemperature())*number/average
		+ getConstantForWeightedAverageOfTemperature();
	}
}